﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Diagnostics;
using RegularExpressionTester.Properties;
using System.Collections.Specialized;

namespace RegularExpressionTester
{
    public partial class RegExpForm : Form
    {
        //wait 1 second to start calculate
        private TimeSpan idleTime = TimeSpan.FromSeconds(1);

        private DateTime lastModifiedTime = DateTime.Now;

        private bool isCalculating;

        private bool hasTextChanged;

        private string oldText = string.Empty;

        private Dictionary<string, string> customRegExp = new Dictionary<string, string>();

        //private Dictionary<string, string> ComboBoxRegExpPredefinedText2Tip;

        private static Regex NodeTextReg = new Regex(@"(?<type>(Match)|(Group)) (?<num>\d+) at position (?<start>-?\d+) to (?<stop>-?\d+):");

        public RegExpForm()
        {
            InitializeComponent();

            initListBoxRegExpOptions();

            initComboBoxRegExpType();
        }

        private void initListBoxRegExpOptions()
        {
            listBoxRegExpOptions.DataSource = Enum.GetValues(typeof(RegexOptions));

            listBoxRegExpOptions.SelectedIndex = 0;
        }

        private void initComboBoxRegExpType()
        {
            comboBoxRegExpType.DataSource = Enum.GetValues(typeof(PreefinedRegExpType));
        }

        private void checkBoxEnableReplace_CheckedChanged(object sender, EventArgs e)
        {
            if (checkBoxEnableReplace.Checked)
                tableLayoutPanel5.ColumnStyles[2].Width = 50;
            else
                tableLayoutPanel5.ColumnStyles[2].Width = 0;
        }

        private void loadRegularExpressions()
        {
            try
            {
                XDocument doc = XDocument.Load("RegExp.xml");

                doc.Root.Elements("RegularExpression").ToList()
                    .ForEach(e => customRegExp.Add(e.Element("Name").Value.Trim(), e.Element("Value").Value.Trim()));
            }
            catch (Exception exp)
            {
                toolStripStatusLabel1.Text = "Error:" + exp.Message;
            }
        }

        private void richTextBoxRegExp_TextChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void fireChange()
        {
            hasTextChanged = true;
            lastModifiedTime = DateTime.Now;
        }

        private void richTextBoxReplaceRegExp_TextChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void richTextBoxText_TextChanged(object sender, EventArgs e)
        {
            //in case only the color is changed
            if (getText() == oldText)
            {
                return;
            }

            fireChange();

            oldText = getText();
        }

        private void Calculate()
        {
            isCalculating = true;

            RegexOptions option = getOption();

            if (isMatch(option))
            {
                setMatch();
                showAllMatches(option);
            }
            else
            {
                setNoMatch();
            }

            isCalculating = false;
        }

        private RegexOptions getOption()
        {
            RegexOptions option = RegexOptions.None;

            foreach (var item in listBoxRegExpOptions.SelectedItems)
            {
                option = option | (RegexOptions)item;
            }
            return option;
        }

        private void setNoMatch()
        {
            labelIsMatch.Text = "NO";
            labelIsMatch.ForeColor = System.Drawing.Color.Red;

            //textBoxMatchText.Text = string.Empty;

            treeViewAllMatches.Nodes.Clear();
        }

        private void setMatch()
        {
            labelIsMatch.Text = "YES";
            labelIsMatch.ForeColor = System.Drawing.Color.Green;
        }

        private bool isMatch(RegexOptions option)
        {
            try
            {
                bool isMatch = Regex.IsMatch(getText(), getRegExp(), option);
                toolStripStatusLabel1.Text = "Match:" + isMatch;
                return isMatch;
            }
            catch (Exception e)
            {
                toolStripStatusLabel1.Text = "Error:" + e.Message;
                return false;
            }
        }

        private string getText()
        {
            if (checkBoxTrimText.Checked)
                return richTextBoxText.Text.Trim();
            else
                return richTextBoxText.Text;
        }

        private string getRegExp()
        {
            if (checkBoxTrimRegExp.Checked)
                return richTextBoxRegExp.Text.Trim();
            else
                return richTextBoxRegExp.Text;
        }

        private string getReplaceRegExp()
        {
            if (checkBoxTrimText.Checked)
                return richTextBoxReplaceRegExp.Text.Trim();
            else
                return richTextBoxReplaceRegExp.Text;
        }

        private void listBoxRegExpOptions_SelectedIndexChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void checkBoxTrimRegExp_CheckedChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void checkBoxTrimText_CheckedChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (!hasTextChanged)
                return;

            if (isCalculating)
                return;

            if (string.IsNullOrEmpty(getRegExp())
                || string.IsNullOrEmpty(getText()))
                return;

            if (DateTime.Now - lastModifiedTime < idleTime)
                return;

            Debugger.Log(2, "parser", "Parse begins at " + DateTime.Now + "\n");

            Calculate();

            Debugger.Log(2, "parser", "Parse ends at " + DateTime.Now + "\n");

            hasTextChanged = false;

        }

        private void clearTextFormat()
        {
            richTextBoxText.SelectAll();
            richTextBoxText.SelectionBackColor = Color.White;
            richTextBoxText.SelectionColor = Color.Black;
            richTextBoxText.DeselectAll();
        }

        /// <summary>
        /// because isMatch() returns true, tree view shouldn't throw any exceptions
        /// but replace reg exp may be illegal
        /// </summary>
        private void showAllMatches(RegexOptions option)
        {
            clearTextFormat();

            Regex regex = new Regex(getRegExp(), option);

            MatchCollection matches = regex.Matches(getText());

            Match[] matchesArray = new Match[matches.Count];
            int position = 0;
            foreach (Match m in matches)
                matchesArray[position++] = m;

            bool showGroup = checkBoxShowGroup.Checked;

            position = 0;

            treeViewAllMatches.Nodes.Clear();

            matchesArray
                .Aggregate
                    (treeViewAllMatches.Nodes,
                    (matchNodes, m)
                        =>
                    {
                        TreeNode currentNode = new TreeNode(
                            string.Format("Match {0} at position {1} to {2}: {3}",
                                ++position,
                                m.Index,
                                m.Index + m.Length - 1,
                                m.Value));

                        matchNodes.Add(currentNode);

                        if (showGroup)
                        {
                            for (int i = 0; i < m.Groups.Count; i++)
                            {
                                currentNode.Nodes.Add(
                                    string.Format(" Group {0} at position {1} to {2}: {3}",
                                        i,
                                        m.Groups[i].Index,
                                        m.Groups[i].Index + m.Groups[i].Length - 1,
                                        m.Groups[i].Value));
                            }
                        }

                        return matchNodes;
                    });

            treeViewAllMatches.ExpandAll();

            if (checkBoxEnableReplace.Enabled && !string.IsNullOrEmpty(getReplaceRegExp()))
            {
                try
                {
                    richTextBoxReplaceText.Text = Regex.Replace(
                               getText(),
                               getRegExp(),
                               getReplaceRegExp(),
                               getOption());
                }
                catch (Exception exp)
                {
                    toolStripStatusLabel1.Text = "Error:" + exp.Message;
                }
            }
        }

        private void comboBoxRegExpType_SelectedIndexChanged(object sender, EventArgs e)
        {
            PreefinedRegExpType type = (PreefinedRegExpType)comboBoxRegExpType.SelectedItem;

            switch (type)
            {
                case PreefinedRegExpType.CharacterEscapes:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpCharacterEscapes);
                    break;
                case PreefinedRegExpType.Substitutions:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpSubstitutions);
                    break;
                case PreefinedRegExpType.CharacterClasses:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpCharacterClasses);
                    break;
                case PreefinedRegExpType.AtomicZeroWidthAssertions:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpAtomicZeroWidthAssertions);
                    break;
                case PreefinedRegExpType.Quantifiers:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpQuantifiers);
                    break;
                case PreefinedRegExpType.GroupingConstructs:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpGroupingConstructs);
                    break;
                case PreefinedRegExpType.BackreferenceConstructs:
                case PreefinedRegExpType.AlternationConstructs:
                case PreefinedRegExpType.MiscellaneousConstructs:
                    SetComboBoxRegExpPredefined(Settings.Default.RegExpBackreferenceAlternationMiscellaneousConstructs);
                    break;
                default:
                    break;
            }

            //comboBoxRegExpPredefined.AutoSizeComboBoxItem();
        }

        private void SetComboBoxRegExpPredefined (StringCollection sc)
        {
            //ComboBoxRegExpPredefinedText2Tip = new Dictionary<string, string>();

            //ComboBoxRegExpPredefinedText2Tip.Add(string.Empty, string.Empty);

            //foreach (String s in sc)
            //{
            //    if (!string.IsNullOrEmpty(s))
            //    {
            //        string[] splits = s.Split(new char[]{':'}, 2);

            //        if (!ComboBoxRegExpPredefinedText2Tip.ContainsKey(splits[0]))
            //        {
            //            ComboBoxRegExpPredefinedText2Tip.Add(splits[0], splits[1]);
            //        }
            //    }
            //}

            //comboBoxRegExpPredefined.DataSource = ComboBoxRegExpPredefinedText2Tip.Keys.ToArray();

            comboBoxRegExpPredefined.DataSource = sc;
        }

        private void comboBoxRegExpPredefined_SelectedIndexChanged(object sender, EventArgs e)
        {
            PreefinedRegExpType type = (PreefinedRegExpType)comboBoxRegExpType.SelectedItem;

            if (string.IsNullOrEmpty(comboBoxRegExpPredefined.Text))
                return;

            string regex = comboBoxRegExpPredefined.Text.Split(':')[0];

            switch (type)
            {
                case PreefinedRegExpType.CharacterEscapes:
                case PreefinedRegExpType.CharacterClasses:
                case PreefinedRegExpType.AtomicZeroWidthAssertions:
                case PreefinedRegExpType.Quantifiers:
                case PreefinedRegExpType.GroupingConstructs:
                case PreefinedRegExpType.BackreferenceConstructs:
                case PreefinedRegExpType.AlternationConstructs:
                case PreefinedRegExpType.MiscellaneousConstructs:
                    richTextBoxRegExp.Text += regex;
                    break;
                case PreefinedRegExpType.Substitutions:
                    richTextBoxReplaceRegExp.Text += regex;
                    break;
                default:
                    break;
            }
        }

        private void treeViewAllMatches_AfterSelect(object sender, TreeViewEventArgs e)
        {
            TreeNode selectedNode = treeViewAllMatches.SelectedNode;

            string text = selectedNode.Text;

            clearTextFormat();

            try
            {
                Match match = NodeTextReg.Match(text);

                int start = int.Parse(match.Groups["start"].Value);
                int stop = int.Parse(match.Groups["stop"].Value);

                if (start < 0 || stop < 0 || start > stop)
                {
                    toolStripStatusLabel1.Text = string.Format("{0} {1} contains nothing.",
                        match.Groups["type"].Value,
                        match.Groups["num"].Value);
                    return;
                }

                richTextBoxText.SelectionStart = start;
                richTextBoxText.SelectionLength = stop - start + 1;

                richTextBoxText.SelectionColor = Color.Yellow;
                richTextBoxText.SelectionBackColor = Color.Black;

                toolStripStatusLabel1.Text = string.Format("{0} {1} is highlighted.",
                    match.Groups["type"].Value,
                    match.Groups["num"].Value);
            }
            catch (Exception exp)
            {
                toolStripStatusLabel1.Text = exp.Message;
            }
        }

        private void checkBoxShowGroup_CheckedChanged(object sender, EventArgs e)
        {
            fireChange();
        }

        private void buttonClearRegExp_Click(object sender, EventArgs e)
        {
            richTextBoxRegExp.Clear();
            richTextBoxReplaceRegExp.Clear();
        }

        private void buttonClearText_Click(object sender, EventArgs e)
        {
            richTextBoxText.Clear();
            richTextBoxReplaceText.Clear();
        }

        private void RegExpForm_Load(object sender, EventArgs e)
        {
            loadRegularExpressions();
            
            customRegExp.ToList()
                .ForEach(pair => comboBoxCustomRegExp.Items.Add(pair.Key));

            timer1.Enabled = true;
        }

        private void comboBoxCustomRegExp_SelectedIndexChanged(object sender, EventArgs e)
        {
            string name = (string)comboBoxCustomRegExp.SelectedItem;

            if (!string.IsNullOrEmpty(name))
            {
                richTextBoxRegExp.Text += customRegExp[name];
            }
        }

        private void comboBoxRegExpPredefined_DrawItem(object sender, DrawItemEventArgs e)
        {
            //// 绘制背景
            //e.DrawBackground();

            ////绘制列表项目
            //e.Graphics.DrawString(comboBoxRegExpPredefined.Items[e.Index].ToString(), e.Font, System.Drawing.Brushes.Black, e.Bounds);
            ////将高亮的列表项目的文字传递到toolTip1(之前建立ToolTip的一个实例)
            //if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
            //    toolTip1.Show(ComboBoxRegExpPredefinedText2Tip[comboBoxRegExpPredefined.Items[e.Index].ToString()], comboBoxRegExpPredefined, e.Bounds.X + e.Bounds.Width, e.Bounds.Y + e.Bounds.Height);
            //e.DrawFocusRectangle();
        }

        private void comboBoxRegExpPredefined_DropDownClosed(object sender, EventArgs e)
        {
            //toolTip1.Hide(comboBoxRegExpPredefined);

            comboBoxRegExpPredefined.toolTip.RemoveAll();
        }

    }
}
